/**
 * 
 */
package com.gragra.sampling.vector;
import java.util.concurrent.atomic.AtomicInteger;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import com.gragra.sampling.CRPProcess;
import com.gragra.sampling.LogarithmicFactorialCash;
import com.gragra.sampling.LogarithmicFactorialPlusAlphaCash;
import com.gragra.sampling.LogarithmicFactorialStartingCash;
import com.gragra.sampling.PopulationSamplingStructure;
import com.gragra.util.OpenUnitElementCounter;
import com.gragra.util.PairNumberer;
/**
 * Assigns probability to a vector based on the crp and the asssumption that the vector entries are discreet counts
 * @author Christoph Teichmann
 * created Apr 29, 2013 6:54:22 PM
 * @version 0.1
 */
public class LimitedClassesUnitCRP extends CRPProcess implements VectorProbabilityAssigner
{
	/**
	 * 
	 */
	private static final int FAKEUNITNUMBERFORLABELS = -1;
	/**
	 * 
	 */
	private final OpenUnitElementCounter[] seen;
	/**
	 * 
	 * @param thread
	 * @return
	 */
	private OpenUnitElementCounter getHolder(int thread)
	{
		this.seen[thread].clear();
		return this.seen[thread];
	}
	/**
	 * 
	 */
	private int free = 0;
	/**
	 * 
	 * @return
	 */
	protected int getFree()
	{return free++;}
	/**
	 * 
	 */
	private final Int2IntOpenHashMap unit2Entry;
	{
		unit2Entry = new Int2IntOpenHashMap();
		unit2Entry.defaultReturnValue(-1);
	}
	/**
	 * 
	 */
	private final LogarithmicFactorialCash[] elementCashes;
	/**
	 * 
	 * @param pos
	 * @return
	 */
	protected LogarithmicFactorialCash getCashElement(int pos)
	{return elementCashes[pos];}
	/**
	 * 
	 */
	private final LogarithmicFactorialCash[] unitCashes;
	/**
	 * 
	 * @param pos
	 * @return
	 */
	protected LogarithmicFactorialCash getCashUnit(int pos)
	{return unitCashes[pos];}
	/**
	 * 
	 */
	private final int threads;
	/**
	 * 
	 */
	private final PairNumberer mapToUnits = new PairNumberer();
	/**
	 * 
	 * @return
	 */
	private int getUnit(int label, int unit)
	{return this.mapToUnits.get(unit, label);}
	/**
	 * 
	 * @param numberOfLabels
	 */
	public LimitedClassesUnitCRP(int numberOfLabels, double[] unitAlpha,double labelAlpha, int threads)
	{
		super(threads);
		if(numberOfLabels < 1)
		{throw new IllegalArgumentException("There has to be at least one label");}
		this.numberOfLabels = numberOfLabels;
		int un;
		mapToUnits.put(FAKEUNITNUMBERFORLABELS, FAKEUNITNUMBERFORLABELS,un = this.getFree());
		this.unit2Entry.put(un, 0);
		for(int label=0;label<numberOfLabels;++label)
		{
			for(int unit=0;unit<unitAlpha.length;++unit)
			{
				mapToUnits.put(unit,label,un = this.getFree());
				this.unit2Entry.put(un, unit+1);
			}
		}
		this.unitCashes = new LogarithmicFactorialCash[unitAlpha.length+1];
		this.elementCashes = new LogarithmicFactorialCash[unitAlpha.length+1];
		this.unitCashes[0] = new LogarithmicFactorialPlusAlphaCash(labelAlpha*numberOfLabels, threads);
		this.elementCashes[0] = new LogarithmicFactorialPlusAlphaCash(labelAlpha, threads);
		for(int i=0;i<unitAlpha.length;++i)
		{
			this.unitCashes[i+1] = new LogarithmicFactorialPlusAlphaCash(unitAlpha[i], threads);
			this.elementCashes[i+1] = new LogarithmicFactorialStartingCash(unitAlpha[i], threads);
		}
		this.threads = threads;
		this.seen = new OpenUnitElementCounter[threads];
		for(int i=0;i<this.seen.length;++i)
		{seen[i] = new OpenUnitElementCounter();}
	}
	/**
	 * 
	 */
	private final int numberOfLabels;
	/* (non-Javadoc)
	 * @see com.gragra.sampling.ProbabilityAssigner#addInitial(com.gragra.sampling.SamplingStructure)
	 */
	@Override
	public void add(PopulationSamplingStructure pss, int thread)
	{
		VectorStructure vs = (VectorStructure) pss;
		OpenUnitElementCounter given = this.getGiven(thread);
		changeFeatures(vs, given, vs.getLabel(thread), thread,1.0);
	}
	/**
	 * 
	 * @param vs 
	 * @param given
	 * @param thread
	 */
	protected void changeFeatures(VectorStructure vs, OpenUnitElementCounter given, int label, int thread, double dir)
	{
		int unit = this.getUnit(FAKEUNITNUMBERFORLABELS, FAKEUNITNUMBERFORLABELS);
		given.add(unit, label, dir);
		double[] values = vs.getValues();
		for(int i=0;i<values.length;++i)
		{
			int u = vs.getCodes()[i][0];
			int element = vs.getCodes()[i][1];
			unit = this.getUnit(label, u);
			given.add(unit, element, dir*values[i]);
		}
	}
	/**
	 * 
	 * @param pss
	 * @param thread
	 * @return
	 */
	@Override
	public double makeLogProb(PopulationSamplingStructure pss, int thread)
	{
		VectorStructure vs = (VectorStructure) pss;
		a.incrementAndGet();
		OpenUnitElementCounter seen = this.getHolder(thread);
		this.changeFeatures(vs, seen, vs.getLabel(thread), thread, 1.0);
		return this.makeLogProb(seen, thread);
	}
	@Override
	public int getPopulationSize()
	{return this.threads;}
	@Override
	public void remove(PopulationSamplingStructure pss, int thread)
	{
		VectorStructure vs = (VectorStructure) pss;
		OpenUnitElementCounter given = this.getGiven(thread);
		changeFeatures(vs, given, vs.getLabel(thread), thread,-1.0);
	}
	@Override
	public double getLocalLogLike(PopulationSamplingStructure pss, int thread)
	{return 0;}
	@Override
	public double getNumberOfEvaluations()
	{return a.get();}
	/**
	 * 
	 */
	private final AtomicInteger a = new AtomicInteger();
	@Override
	public int getLabelNumber(VectorStructure vectorStructure, int thread)
	{return this.numberOfLabels;}
	@Override
	protected LogarithmicFactorialCash getUnitCash(int unit)
	{
		int i = this.unit2Entry.get(unit);
		if(i >= 0)
		{return this.unitCashes[i];}
		return null;
	}
	@Override
	protected LogarithmicFactorialCash getElementCash(int unit, int element)
	{
		int i = this.unit2Entry.get(unit);
		if(i >= 0)
		{return this.elementCashes[i];}
		return null;
	}
	@Override
	public double getNumberOfAuxEvals()
	{return 0;}
	@Override
	public void reduceStructuralAnnealing(int p)
	{}
}